/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.google.code.peersim.pastry.controls;

import com.google.code.peersim.pastry.protocol.PastryNode;
import com.google.code.peersim.pastry.protocol.PastryProtocolState;
import com.google.code.peersim.pastry.protocol.PastryResource;
import com.google.code.peersim.pastry.protocol.PastryResourceAssignLsnrIfc;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.HashSet;
import java.util.Set;
import peersim.config.Configuration;
import peersim.core.CommonState;
import peersim.core.Control;
import peersim.core.Network;
import peersim.util.FileNameGenerator;
import peersim.util.IncrementalStats;

/**
 * This <i>control</i> class randomly selects an active node in the built Pastry
 * network and asks it to push a resource into the network.
 *
 * @author frusso
 * @version 0.1
 * @since 0.1
 */
public class PastryResourceAssigner implements Control, PastryResourceAssignLsnrIfc {

  /**
   * Configures how many resources should be assigned.
   */
  public static final String RESOURCES = "resources";
  /**
   * Configures the minimum simulated-time required for starting spreading resources.
   */
  public static final String START_TIME = "startTime";
  /**
   * The log file to log to.
   */
  public static final String LOG_FILE = "log";
  /**
   * Whether to log or not.
   */
  public static final String DO_LOG = "doLog";
  /**
   * Whether the component is enabled or not.
   */
  private static boolean enabled = true;

  /**
   * Enables the component.
   */
  public static void enable() {
    enabled = true;
  }

  /**
   * Disables the component.
   */
  public static void disable() {
    enabled = false;
  }

  private long startTime;
  private final int resources;
  private int publishedResources = 0;
  private int assignedResources = 0;
  private PrintStream stream;
  private String fileName;
  private IncrementalStats stats;
  private boolean doLog;
  /**
   * Resources whose publishing activity has begun, but not already assigned to any node.
   */
  private Set<PastryResource<?>> resourcesWaitingToBePublished = new HashSet<PastryResource<?>>();

  /**
   * Constructor.
   * @param prefix
   */
  public PastryResourceAssigner(String prefix) throws FileNotFoundException {
    resources = Configuration.getInt(prefix+"."+RESOURCES);
    startTime = Configuration.getInt(prefix+"."+START_TIME);
    doLog = Configuration.getBoolean(prefix + "." + DO_LOG);
    if(doLog) {
      fileName = new FileNameGenerator(Configuration.getString(prefix + "."+LOG_FILE), ".log").nextCounterName();
      stream = new PrintStream(new FileOutputStream(fileName));
    }
    stats = new IncrementalStats();
  }

  /**
   * {@inheritDoc}
   *
   * Performs iff the component is enabled and the current simulated-time is within
   * the configured time-window.
   *
   * @see PastryResourceAssigner#enable()
   * @see PastryResourceAssigner#disable()
   */
  @Override
  public boolean execute() {
    boolean stop = false;
    if(enabled && CommonState.getTime()>=startTime && publishedResources<resources) {
      publishResource();
    }
    if(CommonState.getTime()==CommonState.getEndTime()-1) {
      printFinalStats();
    }
    return stop;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void resourceAssigned(ResourceAssignedInfo info) {
    if(doLog) {
      stream.println((assignedResources+1)+") "+ info.getResource().getResourceId()+" "+info.getHops());
    }
    assignedResources++;
    stats.add(info.getHops());
    AssignedResources.addAssignedResource(info.getResource());
    resourcesWaitingToBePublished.remove(info.getResource());
    printStats();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void resourceReceived(ResourceReceivedInfo info) {
    // NOP
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void resourceRouted(ResourceRoutedInfo info) {
    // NOP
  }

  private void printFinalStats() {
    System.err.println(this.getClass().getName()+"\nAvg-hops: "+stats.getAverage()+"\nMax-hops: "+stats.getMax()+"\nMin-hops: "+
      stats.getMin()+"\nStddev-hops: "+stats.getStD()+"\nVar-hops: "+stats.getVar()+"\nAssigned resources: "+stats.getN()+
      "\nPublished resources: "+publishedResources+"\nPending resources: ("+resourcesWaitingToBePublished.size()+") "+resourcesWaitingToBePublished+"\n");
  }

  private void printStats() {
    if(doLog)
      stream.println("Avg-hops: "+stats.getAverage()+" Max-hops: "+stats.getMax()+" Min-hops: "+stats.getMin()+" Stddev-hops: "+stats.getStD()+" Var-hops: "+stats.getVar());
  }

  /**
   * Randomly selects a node in the Pastry network and asks it to send a resource
   * to the network itself. The node has to be in state {@link PastryProtocolState#JOINED}.
   */
  private void publishResource() {
    PastryNode node = null;
    int dim = Network.size();
    int i = 0;
    do {
      int index = CommonState.r.nextInt(Network.size());
      node = (PastryNode) Network.get(index);
      i++;
    } while(!node.isJoined() && i<dim);

    if(node.isJoined()) {
      node.addResourceAssignListener(this);
      PastryResource<String> res = AssignedResources.createResource("Res. "+publishedResources++);
      resourcesWaitingToBePublished.add(res);
      node.publishResource(res);
    } else {
      // NOP, there are no joined nodes
    }
  }
}